use std::error::Error;

use cs2s::{cache::CacheTableClientHandle, sign::CodeSign, utils::mangle::PathStrExt};
use itertools::Itertools;
use tabled::Tabled;

use crate::{
    args::TableShowCommand,
    table::visualize::{visualize, Style},
    ARGS,
};

/// Displays the detail of a cache table.
pub fn show(command: &TableShowCommand) -> Result<(), Box<dyn Error>> {
    let name = format!("{}-{}", ARGS.name, command.table.mangle());
    let handle = CacheTableClientHandle::new(&name)?;

    if let Some(index) = command.slot {
        show_slot(command.shared_args.style, handle, index)?;
    } else {
        show_slots(command.shared_args.style, handle);
    }

    Ok(())
}

/// Show the detail of a slot.
fn show_slot(
    style: Style,
    handle: CacheTableClientHandle,
    index: usize,
) -> Result<(), Box<dyn Error>> {
    #[derive(Tabled)]
    struct BlockListing {
        #[tabled(
            rename = "Guest Code Region",
            format("{:#x}-{:#x}", self.guest_region.0, self.guest_region.0 + self.guest_region.1),
        )]
        guest_region: (usize, usize),

        #[tabled(rename = "Guest Code Signature")]
        guest_sign: CodeSign,

        #[tabled(rename = "Host Meta Size")]
        host_meta_size: usize,

        #[tabled(rename = "Host Meta")]
        host_meta: String,

        #[tabled(rename = "Host Code Size")]
        host_code_size: usize,

        #[tabled(rename = "Host Code")]
        host_code: String,
    }

    let Some(slot) = handle.iter_slots().nth(index) else {
        return Err(format!("Slot {} not found", index).into());
    };

    let blocks = slot.map(|(key, val)| {
        let trunc = |data: &[u8], len: usize| {
            let mut s = if data.len() > len {
                String::with_capacity(len * 2 + 3)
            } else {
                String::with_capacity(data.len() * 2)
            };
            for byte in data.iter().take(len) {
                s.push_str(&format!("{:02x}", byte));
            }
            if data.len() > len {
                s.push_str("...");
            }
            s
        };

        BlockListing {
            guest_region: (key.guest_addr, key.guest_size),
            guest_sign: key.guest_sign.clone(),
            host_meta_size: val.host_meta.len(),
            host_meta: trunc(val.host_meta, 16),
            host_code_size: val.host_code.len(),
            host_code: trunc(val.host_code, 16),
        }
    });

    visualize(style, blocks);

    Ok(())
}

/// Show all non-empty slots in the table.
fn show_slots(style: Style, handle: CacheTableClientHandle) {
    #[derive(Tabled)]
    #[tabled(rename_all = "Pascal")]
    struct SlotListing {
        indexes: String,
        count: usize,
    }

    let chunks = handle
        .iter_slots()
        .map(Iterator::count)
        .enumerate()
        .chunk_by(|&(_, count)| count);

    let slots = chunks.into_iter().filter_map(|(count, slots)| {
        let slots = slots.map(|(i, _)| i).collect_vec();
        let indexes = if let [first, .., last] = slots.as_slice() {
            format!("{} ~ {}", first, last)
        } else if let Some(first) = slots.first() {
            first.to_string()
        } else {
            return None;
        };
        Some(SlotListing { indexes, count })
    });

    visualize(style, slots);
}
